<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\WPush;

use Srv\Apps\Proto\VO\Common\PushMsgVO;
use Srv\Conf\ConfModel\WPushConf;
use Srv\Libs\Boot;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommNet;
use Srv\Libs\Frame\Logs;

class WooshPushMsg
{
    private string $pushApiUrl                      = 'https://cp.pushwoosh.com/json/1.3';  // 推送地址
    private string $appKey                          = '';           // 应用Key
    private string $secret                          = '';           // 秘钥Key
    private string $logPath                         = '';           // 日志路径
    private string $userThreadIdPrefix              = 'U_';         // push-threadId-用户
    private string $groupThreadIdPrefix             = 'G_';         // push-threadId-群组
    private string $userTagPrefix                   = 'u';          // 用户标签前缀[必须单个字母]
    private string $userAliaPrefix                  = 'a';          // 用户别名前缀[必须单个字母]
    private string $groupTagPrefix                  = 's';          // 群组标签订阅前缀[必须单个字母]
    private string $groupTagSubPrefix               = 'g';          // 群组标签前缀[必须单个字母]
    public const TAG_NAME_ALIAS                     = '__Alias';    // 用户别名名
    public const TAG_NAME_GROUP_LIST                = '__GroupList';// 群组列表名

    /**
     * @param WPushConf $WPushConf
     * __construct
     */
    public function __construct(WPushConf $WPushConf)
    {
        $this->appKey                   = $WPushConf->getAppKey();
        $this->secret                   = $WPushConf->getSecret();
        $this->logPath                  = $WPushConf->getLogPath();
        if(!Boot::isRelease()){     // 开发环境
            $this->userTagPrefix        = 'v';
            $this->userAliaPrefix       = 'b';
            $this->groupTagPrefix       = 't';
            $this->groupTagSubPrefix    = 'h';
        }
    }

    /**
     * @param int $userId
     * @return string
     * getThreadIdByUser
     */
    final public function getThreadIdByUser(int $userId):string
    {
        return $this->userThreadIdPrefix.$userId;
    }

    /**
     * @param int $groupId
     * @return string
     * getThreadIdByGroup
     */
    final public function getThreadIdByGroup(int $groupId):string
    {
        return $this->groupThreadIdPrefix.$groupId;
    }

    /**
     * @param int $userId
     * @return string
     * 获取推送用户标签名
     */
    final public function getPushTagNameByUser(int $userId):string
    {
        return $this->userTagPrefix.$userId;
    }

    /**
     * @param int $userId
     * @return string
     * 获取推送用户别名
     */
    final public function getPushAliaNameByUser(int $userId):string
    {
        return $this->userAliaPrefix.$userId;
    }

    /**
     * @param int $groupId
     * @return string
     * 获取推送群组标签名
     */
    final public function getPushTagNameByGroup(int $groupId):string
    {
        return $this->groupTagPrefix.$groupId;
    }

    /**
     * @param int $groupId
     * @return string
     * 获取推送群组标签名
     */
    final public function getPushTagNameByGroupSub(int $groupId):string
    {
        return $this->groupTagSubPrefix.$groupId;
    }

    /**
     * @param PushMsgVO $PushMsgVO
     * @return bool
     * 发送全体推送消息
     */
    public function sendPushAllMsg(PushMsgVO $PushMsgVO):bool
    {
        return $this->sendPushMsg($PushMsgVO, [], [], [], true);
    }

    /**
     * @param PushMsgVO $PushMsgVO
     * @param array $userIdRecvList
     * @return bool
     * 发送用户所有设备推送消息
     */
    public function sendPushUserAllDeviceMsg(PushMsgVO $PushMsgVO, array $userIdRecvList):bool
    {
        if(count($userIdRecvList) < 1) return false;
        $userIdStrList              = [];
        foreach($userIdRecvList as $userIdRecv){
            $userIdStrList[]        = strval($userIdRecv);
        }
        $userIdStrList              = array_unique($userIdStrList);
        if(count($userIdStrList) < 1) return false;
        return $this->sendPushMsg($PushMsgVO, [], $userIdStrList, []);
    }

    /**
     * @param PushMsgVO $PushMsgVO
     * @param array $userIdRecvList
     * @return bool
     * 发送用户当前或最后设备推送消息
     */
    public function sendPushUserMsg(PushMsgVO $PushMsgVO, array $userIdRecvList):bool
    {
        if(count($userIdRecvList) < 1) return false;
        $tagUserNameRecvList        = [];
        foreach($userIdRecvList as $userIdRecv){
            $tagUserNameRecvList[]  = $this->getPushAliaNameByUser($userIdRecv);
        }
        $inList                     = array_unique($tagUserNameRecvList);
        if(count($inList) < 1) return false;
        $conditionList              = [[self::TAG_NAME_ALIAS, 'IN', $inList]];
        return $this->sendPushMsg($PushMsgVO, [$conditionList, 'AND'], [], []);
    }

    /**
     * @param PushMsgVO $PushMsgVO
     * @param int $groupId
     * @param array $userIdRecvList
     * @param array $userIdNotList
     * @return bool
     * 发送群组订阅推送消息
     */
    public function sendPushGroupSubMsg(PushMsgVO $PushMsgVO, int $groupId, array $userIdRecvList = [], array $userIdNotList = []):bool
    {
        if($groupId < 1) return false;
        $tagGroupName               = $this->getPushTagNameByGroupSub($groupId);
        $tagUserNameRecvList        = [];
        $tagUserNameNotList         = [];
        if(count($userIdRecvList) > 0) foreach($userIdRecvList as $userIdRecv){
            $tagUserNameRecvList[]  = $this->getPushTagNameByUser($userIdRecv);
        }
        if(count($userIdNotList) > 0) foreach($userIdNotList as $userIdNot){
            $tagUserNameNotList[]   = $this->getPushTagNameByUser($userIdNot);
        }
        $inList                     = array_unique(array_merge([$tagGroupName], $tagUserNameRecvList));
        if(count($inList) < 1) return false;
        $conditionList              = [[self::TAG_NAME_GROUP_LIST, 'IN', $inList]];
        if(count($tagUserNameNotList) > 0){
            $tagUserNameNotList     = array_unique($tagUserNameNotList);
            $conditionList[]        = [self::TAG_NAME_GROUP_LIST, 'NOTIN', $tagUserNameNotList];
        }
        return $this->sendPushMsg($PushMsgVO, [$conditionList, 'AND'], [], []);
    }

    /**
     * @param PushMsgVO $PushMsgVO
     * @param int $groupId
     * @param array $userIdNotList
     * @return bool
     * 发送群组推送消息
     */
    public function sendPushGroupMsg(PushMsgVO $PushMsgVO, int $groupId, array $userIdNotList = []):bool
    {
        if($groupId < 1) return false;
        $tagGroupName               = $this->getPushTagNameByGroup($groupId);
        $tagUserNameNotList         = [];
        if(count($userIdNotList) > 0) foreach($userIdNotList as $userIdNot){
            $tagUserNameNotList[]   = $this->getPushTagNameByUser($userIdNot);
        }
        $inList                     = [$tagGroupName];
        if(count($inList) < 1) return false;
        $conditionList              = [[self::TAG_NAME_GROUP_LIST, 'IN', $inList]];
        if(count($tagUserNameNotList) > 0){
            $tagUserNameNotList     = array_unique($tagUserNameNotList);
            $conditionList[]        = [self::TAG_NAME_GROUP_LIST, 'NOTIN', $tagUserNameNotList];
        }
        return $this->sendPushMsg($PushMsgVO, [$conditionList, 'AND'], [], []);
    }

    /**
     * @param PushMsgVO $PushMsgVO
     * @param array $conditionData
     * @param array $userIdList
     * @param array $deviceList
     * @param bool $isAll
     * @return bool
     * 发送推送消息
     */
    private function sendPushMsg(PushMsgVO $PushMsgVO, array $conditionData = [], array $userIdList = [], array $deviceList = [], bool $isAll = false):bool
    {
        if(!$isAll && count($conditionData) < 1 && count($userIdList) < 1 && count($deviceList) < 1) return false;
        $pushData       = [
            'uId'       => $PushMsgVO->getUId(),
            'type'      => $PushMsgVO->getType(),
            'toUrl'     => $PushMsgVO->getToUrl(),
        ];
        $badgeNum       = $PushMsgVO->getBadgeNum() < 0 ? '+1' : $PushMsgVO->getBadgeNum();
        $threadId       = $PushMsgVO->getThreadId();
        $pushTitle      = $PushMsgVO->getTitle();
        $pushContent    = $PushMsgVO->getContent();
        $pushSound      = $PushMsgVO->getSound();
        $pushIcon       = $PushMsgVO->getIcon();
        $pushTtl        = $PushMsgVO->getTtl() < 1 ? 43200 : $PushMsgVO->getTtl();
        $pushSilent     = $PushMsgVO->getSilent() === 1 ? 1 : 0;
        $conditions                 = $conditionData[0]??[];
        $conditionsOperator         = strtoupper(strval($conditionData[1]??''));
        if(!is_array($conditions)) $conditions = [];
        if(!in_array($conditionsOperator, ['AND', 'OR'], true)) $conditionsOperator = 'AND';
        if(!$isAll && count($userIdList) < 1 && count($deviceList) < 1 && count($conditions) < 1) return false; // 防止全体推送,必须要有:用户或设备或条件
        $notifications  = [
            'send_date'             => 'now',
            'ignore_user_timezone'  => true,
            'content'               => $pushContent,
            'title'                 => $pushTitle,
            'data'                  => $pushData,
            'platforms'             => [1, 3, 17],
            'send_rate'             => 1000,
            // object
            'devices'               => $isAll ? [] : $deviceList,
            'users'                 => $isAll ? [] : $userIdList,
            'conditions'            => $isAll ? [] : $conditions,
            'conditions_operator'   => $conditionsOperator,
            // iOS
            'ios_badges'            => $badgeNum,
            'ios_sound'             => $pushSound,
            'apns_trim_content'     => 1,
            'ios_title'             => $pushTitle,
            'ios_thread_id'         => $threadId,
            'ios_ttl'               => $pushTtl,
            'ios_silent'            => $pushSilent,
            // Android
            'android_badges'        => $badgeNum,
            'android_sound'         => $pushSound,
            'android_custom_icon'   => $pushIcon,
            'android_gcm_ttl'       => $pushTtl,
            'android_silent'        => $pushSilent,
        ];
        if(count($notifications['devices']) < 1) unset($notifications['devices']);
        if(count($notifications['users']) < 1) unset($notifications['users']);
        if(count($notifications['conditions']) < 1) unset($notifications['conditions']);
        if(strlen($notifications['conditions_operator']) < 1) unset($notifications['conditions_operator']);
        if(strlen($notifications['title']) < 1) unset($notifications['title']);
        if(strlen($notifications['ios_title']) < 1) unset($notifications['ios_title']);
        if(strlen($notifications['ios_sound']) < 1) unset($notifications['ios_sound']);
        if(strlen($notifications['ios_thread_id']) < 1) unset($notifications['ios_thread_id']);
        if(strlen($notifications['android_sound']) < 1) unset($notifications['android_sound']);
        if(strlen($notifications['android_custom_icon']) < 1) unset($notifications['android_custom_icon']);
        $postData       = [
            'request'   => [
                'application'   => $this->appKey,
                'auth'          => $this->secret,
                'notifications' => [$notifications],
            ]
        ];
        $apiData        = $this->pushwooshApi('/createMessage', $postData);
        return intval($apiData['status_code']??0) === 200;
    }

    /**
     * @param array $regIdList
     * @param string $msgText
     * @param string $title
     * @param array $extData
     * @param array $pushSet
     * @return bool
     * sendToRegIdList
     */
    public function sendToRegIdList(array $regIdList, string $msgText, string $title = '', array $extData = [], array $pushSet = []):bool
    {
        return $this->sendPushMsgSrc([], $regIdList, $msgText, $title, $extData, $pushSet);
    }

    /**
     * @param array $aliasList
     * @param array $tagList
     * @param string $msgText
     * @param string $title
     * @param array $extData
     * @param array $pushSet
     * @return bool
     * sendToTagAliasList
     */
    public function sendToTagAliasList(array $aliasList, array $tagList, string $msgText, string $title = '', array $extData = [], array $pushSet = []):bool
    {
        $aliasListSize              = count($aliasList);
        $tagListSize                = count($tagList);
        if($aliasListSize < 1 && $tagListSize < 1) return false;
        $conditionList              = [];
        if($aliasListSize > 0) $conditionList[] = [self::TAG_NAME_ALIAS, 'IN', $aliasList];
        if($tagListSize > 0) $conditionList[] = [self::TAG_NAME_GROUP_LIST, 'IN', $tagList];
        if(count($conditionList) < 1) return false;
        return $this->sendPushMsgSrc([$conditionList, 'OR'], [], $msgText, $title, $extData, $pushSet);
    }

    /**
     * @param array $conditionData
     * @param array $deviceList
     * @param string $msgText
     * @param string $title
     * @param array $extData
     * @param array $pushSet
     * @return bool
     * sendPushMsgSrc
     */
    private function sendPushMsgSrc(array $conditionData, array $deviceList, string $msgText, string $title = '', array $extData = [], array $pushSet = []):bool
    {
        if(count($conditionData) < 1 && count($deviceList) < 1) return false;
        if(strlen($msgText) < 1) return false;
        $badgeNum       = strval($pushSet['badge']??'');
        if(strlen($badgeNum) < 1) $badgeNum = '+1';
        $threadId       = strval($pushSet['thread-id']??'');
        $pushTitle      = $title;
        $pushContent    = $msgText;
        $pushSound      = strval($pushSet['sound']??'');
        $pushIcon       = strval($pushSet['large_icon']??'');
        $pushTtl        = 43200;
        $pushSilent     = 0;
        $conditions                 = $conditionData[0]??[];
        $conditionsOperator         = strtoupper(strval($conditionData[1]??''));
        if(!is_array($conditions)) $conditions = [];
        if(!in_array($conditionsOperator, ['AND', 'OR'], true)) $conditionsOperator = 'AND';
        if(count($deviceList) < 1 && count($conditions) < 1) return false;  // 防止全体推送,必须要有:用户或设备或条件
        $notifications  = [
            'send_date'             => 'now',
            'ignore_user_timezone'  => true,
            'content'               => $pushContent,
            'title'                 => $pushTitle,
            'data'                  => $extData,
            'platforms'             => [1, 3, 17],
            'send_rate'             => 1000,
            // object
            'devices'               => $deviceList,
            'users'                 => [],
            'conditions'            => $conditions,
            'conditions_operator'   => $conditionsOperator,
            // iOS
            'ios_badges'            => $badgeNum,
            'ios_sound'             => $pushSound,
            'apns_trim_content'     => 1,
            'ios_title'             => $pushTitle,
            'ios_thread_id'         => $threadId,
            'ios_ttl'               => $pushTtl,
            'ios_silent'            => $pushSilent,
            // Android
            'android_badges'        => $badgeNum,
            'android_sound'         => $pushSound,
            'android_custom_icon'   => $pushIcon,
            'android_gcm_ttl'       => $pushTtl,
            'android_silent'        => $pushSilent,
        ];
        if(count($notifications['devices']) < 1) unset($notifications['devices']);
        if(count($notifications['users']) < 1) unset($notifications['users']);
        if(count($notifications['conditions']) < 1) unset($notifications['conditions']);
        if(strlen($notifications['conditions_operator']) < 1) unset($notifications['conditions_operator']);
        if(strlen($notifications['title']) < 1) unset($notifications['title']);
        if(strlen($notifications['ios_title']) < 1) unset($notifications['ios_title']);
        if(strlen($notifications['ios_sound']) < 1) unset($notifications['ios_sound']);
        if(strlen($notifications['ios_thread_id']) < 1) unset($notifications['ios_thread_id']);
        if(strlen($notifications['android_sound']) < 1) unset($notifications['android_sound']);
        if(strlen($notifications['android_custom_icon']) < 1) unset($notifications['android_custom_icon']);
        $postData       = [
            'request'   => [
                'application'   => $this->appKey,
                'auth'          => $this->secret,
                'notifications' => [$notifications],
            ]
        ];
        $apiData        = $this->pushwooshApi('/createMessage', $postData);
        return intval($apiData['status_code']??0) === 200;
    }

    /**
     * @param string $path
     * @param $postData
     * @return array
     * pushwooshApi
     */
    private function pushwooshApi(string $path, $postData):array
    {
        if(strlen($path) < 1) return [];
        $headerData     = [
            'Content-Type'      => 'application/json',
        ];
        $postString     = count($postData) > 0 ?  CommJson::encodeArray($postData) : '';
        $param          = ['TIMEOUT' => 10, 'METHOD' => 'POST', 'POST_DATA' => $postString, 'HEADER' => $headerData];
        $resData        = CommNet::getUrlInfo($this->pushApiUrl.$path, $param);
        $apiBody        = trim($resData['body']??'');
        if(strlen($this->logPath) > 0) Logs::debug($path.', body='.$apiBody.', post='.$postString, __LINE__, __FILE__, $this->logPath);
        return CommJson::decodeArray($apiBody);
    }

    /**
     * @param string $pushRegId
     * @param array $apiData
     * @return bool
     * getTagsByPushRegId
     */
    public function getTagsByPushRegId(string $pushRegId, array &$apiData):bool
    {
        $postData       = [
            'request'   => [
                'application'   => $this->appKey,
                'auth'          => $this->secret,
                'hwid'          => $pushRegId,
            ]
        ];
        $apiData        = $this->pushwooshApi('/getTags', $postData);
        return intval($apiData['status_code']??0) === 200;
    }

    /**
     * @param array $pushRegIdList
     * @return bool
     * clearTagsByPushRegIdBatch
     */
    public function clearTagsByPushRegIdBatch(array $pushRegIdList):bool
    {
        if(count($pushRegIdList) < 1) return false;
        $tags   = [self::TAG_NAME_ALIAS => null, self::TAG_NAME_GROUP_LIST => null];   // 删除tag
        return $this->setTagsByPushRegIdBatch($pushRegIdList, $tags);
    }

    /**
     * @param string $pushRegId
     * @param string $alias
     * @param array $tagList
     * @return bool
     * setTagsByPushRegId
     */
    public function setTagsByPushRegId(string $pushRegId, string $alias, array $tagList):bool
    {
        $tags   = [self::TAG_NAME_ALIAS => strlen($alias) > 0 ? $alias : null, self::TAG_NAME_GROUP_LIST => count($tagList) > 0 ? $tagList : null];
        return $this->setTagsByPushRegIdBatch([$pushRegId], $tags);
    }

    /**
     * @param array $pushRegIdList
     * @param array $tags
     * @return bool
     * setTagsByPushRegIdBatch
     */
    public function setTagsByPushRegIdBatch(array $pushRegIdList, array $tags):bool
    {
        if(count($pushRegIdList) < 1 || count($tags) < 1) return false;
        $bulkSetTagsList    = [];
        foreach ($pushRegIdList as $pushRegId){
            if(!is_string($pushRegId) || strlen($pushRegId) < 1) continue;
            $bulkSetTagsList[$pushRegId] = $tags;
        }
        return $this->bulkSetTagsBatch($bulkSetTagsList);
    }

    /**
     * @param array $bulkSetTagsList
     * @return bool
     * bulkSetTagsBatch
     */
    public function bulkSetTagsBatch(array $bulkSetTagsList):bool
    {
        if(count($bulkSetTagsList) < 1) return false;
        $devicesList        = [];
        foreach ($bulkSetTagsList as $pushRegId => $tags){
            if(!is_string($pushRegId) || !is_array($tags) || strlen($pushRegId) < 1 || count($tags) < 1) continue;
            $devicesList[]  = [
                'list_operator' => 'set',
                'hwid'          => $pushRegId,
                'tags'          => $tags,
            ];
        }
        $postData       = [
            'request'   => [
                'application'           => $this->appKey,
                'auth'                  => $this->secret,
                'create_missing_tags'   => false,
                'devices'               => $devicesList,
            ]
        ];
        $apiData        = $this->pushwooshApi('/bulkSetTags', $postData);
        return intval($apiData['status_code']??0) === 200;
    }

    /**
     * @param string $pushRegId
     * @return bool
     * unRegisterDeviceByPushRegId
     */
    public function unRegisterDeviceByPushRegId(string $pushRegId):bool
    {
        if(strlen($pushRegId) < 1) return false;
        $postData       = [
            'request'   => [
                'application'           => $this->appKey,
                'auth'                  => $this->secret,
                'hwid'                  => $pushRegId,
            ]
        ];
        $apiData        = $this->pushwooshApi('/unregisterDevice', $postData);
        return intval($apiData['status_code']??0) === 200;
    }

    /**
     * @param array $pushRegIdList
     * @return bool
     * unRegisterDeviceByPushRegIdBatch
     */
    public function unRegisterDeviceByPushRegIdBatch(array $pushRegIdList):bool
    {
        if(count($pushRegIdList) < 1) return false;
        $result         = true;
        foreach ($pushRegIdList as $pushRegId){
            if(!is_string($pushRegId) || strlen($pushRegId) < 1) continue;
            if(!$this->unRegisterDeviceByPushRegId($pushRegId)) $result = false;
        }
        return $result;
    }

    /**
     * @param string $pushRegId
     * @return bool
     * deleteDeviceByPushRegId
     */
    public function deleteDeviceByPushRegId(string $pushRegId):bool
    {
        if(strlen($pushRegId) < 1) return false;
        $postData       = [
            'request'   => [
                'application'           => $this->appKey,
                'auth'                  => $this->secret,
                'hwid'                  => $pushRegId,
            ]
        ];
        $apiData        = $this->pushwooshApi('/deleteDevice', $postData);
        return intval($apiData['status_code']??0) === 200;
    }

    /**
     * @param array $pushRegIdList
     * @return bool
     * deleteDeviceByPushRegIdBatch
     */
    public function deleteDeviceByPushRegIdBatch(array $pushRegIdList):bool
    {
        if(count($pushRegIdList) < 1) return false;
        $result         = true;
        foreach ($pushRegIdList as $pushRegId){
            if(!is_string($pushRegId) || strlen($pushRegId) < 1) continue;
            if(!$this->deleteDeviceByPushRegId($pushRegId)) $result = false;
        }
        return $result;
    }
}